package nba2kmodtool.filedesc;

import java.io.File;
import java.io.IOException;

import nba2kmodtool.util.ByteFileReader;

/**
 * NBA2K Game Package File Reader
 * @company ZSword (C) Copyright
 * @author JemiZhuu(周士淳)
 * @category N2kPackageFileReader
 * @version 1.0
 * @since 2011-11-16
 */
public class N2kPackageFileReader {
	private static final boolean IS_DEBUG = BaseIffFile.IS_DEBUG;

	protected BaseIffFile iff;
	protected long fileType;
	protected File file;
	protected File cdfFile;

	protected N2kPackageFileReader(BaseIffFile iff) {
		this.iff = iff;
		this.fileType = iff.fileType;
		this.file = iff.file;
		this.cdfFile = iff.cdfFile;
	}

	/**
	 * Read Package File
	 * @param index
	 * @return
	 * @throws IOException
	 */
	public N2kPackageFile readPackageFile(int index) throws IOException {
		if (BaseIffFile.PACKAGE_TYPE != iff.dataFiles[index].fileType) {
			throw new IOException("This File is Not a valid N2k PackageFile");
		}
		IffDataFile bigFile = iff.dataFiles[index];
		if (bigFile.partNum <= 1) {
			throw new IOException(String.format("This N2k PackageFile is Not Support: HAddr:%X - Num:%d",
					bigFile.partAddr[0], bigFile.partNum));
		}
		N2kPackageFile dataFile = new N2kPackageFile();
		ByteFileReader in = new ByteFileReader(file, "r");
		long baseAddr = bigFile.partAddr[0];
		long dataBaseAddr = bigFile.partAddr[1];
		in.seek(baseAddr);
		if (IS_DEBUG) {
			System.out.printf("BaseAddr: %X\n", in.getFilePointer());
		}
		dataFile.serial = in.readLong();
		//type1 files
		in.skipBytes(0x18);
		//texture files
		dataFile.textureNum = in.read4BytesAsInt();
		dataFile.texturesHeaderStart = in.getFilePointer() + in.read4BytesAsInt() - 1;
		//type2 files
		dataFile.modelDef1 = in.read4BytesAsInt();
		dataFile.modelVersion = in.read4BytesAsInt();
		//type3 files
		dataFile.modelDef2 = in.read4BytesAsInt();
		int unknowNum1 = in.read4BytesAsInt();
		long unknowAddr1 = in.getFilePointer() + in.read4Bytes() - 1;
		int unknownNum2 = in.read4BytesAsInt();
		long unknowAddr2 = in.getFilePointer() + in.read4Bytes() - 1;
		//3d model files
		dataFile.modelNumAddr = in.getFilePointer();
		dataFile.modelNum = in.read4BytesAsInt();
		dataFile.modelHeaderStart = in.getFilePointer() + in.read4BytesAsInt() - 1;
		//type5 files
		int unknowNum3 = in.read4BytesAsInt();
		long unknowAddr3 = in.getFilePointer() + in.read4BytesAsInt() - 1;
		if (IS_DEBUG) {
			System.out.printf("TextureDefine: num:%d, headerStart:%X\n", dataFile.textureNum,
					dataFile.texturesHeaderStart);
			System.out.printf("ModelVersion: %d, %X, %d, %X, %X\n", dataFile.modelDef1, dataFile.modelVersion,
					dataFile.modelDef2, unknowAddr2, unknowNum3);
		}
		this.readPackageTextures(in, bigFile, dataFile, dataBaseAddr);
		this.readPackageModels(in, dataFile, dataBaseAddr);
		in.close();
		return dataFile;
	}

	/**
	 * Read Textures from Package File
	 * @param in
	 * @param bigFile
	 * @param dataFile
	 * @param dataBaseAddr
	 * @throws IOException
	 */
	private void readPackageTextures(ByteFileReader in, IffDataFile bigFile, N2kPackageFile dataFile, long dataBaseAddr)
			throws IOException {
		//read dds headers
		in.seek(dataFile.texturesHeaderStart);
		dataFile.textures = new N2kTextureFile[dataFile.textureNum];
		long curPos = in.getFilePointer();
		for (int i = 0; i < dataFile.textureNum; i++) {
			in.seek(curPos + i * 0xF0);
			N2kTextureFile texture = new N2kTextureFile();
			texture.headerStart = in.getFilePointer();
			texture.serial = in.readLong();
			byte[] buf = new byte[4];
			in.read(buf);
			texture.pixelFormat = new String(buf);
			if (buf[0] != 'D' || buf[1] != 'X' || buf[2] != 'T') {
				texture.bmpFlag = buf[0];
				texture.pixelFormat = "BMP";
				texture.fileType = "bmp";
			} else {
				texture.fileType = "dds";
			}
			in.skipBytes(0x5B);
			texture.flag = (short) in.readBytesAsInt(1);
			texture.width = in.readBytesAsInt(2);
			texture.height = in.readBytesAsInt(2);
			if ("BMP".equals(texture.pixelFormat)) {
				if (texture.bmpFlag == 0x32) {
					texture.width = 512;
					texture.height = 128;
				}
				if (texture.bmpFlag == 0x51) {
					texture.width = 256;
					texture.height = 128;
				}
			}
			in.skipBytes(8);
			if (bigFile.indexType > 0) {
				texture.indexType = bigFile.indexType;
				texture.index = bigFile.index;
			} else {
				texture.dataStart = dataBaseAddr + in.read4Bytes() - 1;
			}
			long size = (texture.width * texture.height);
			long minSize = 16;
			if ("DXT1".equals(texture.pixelFormat)) {
				size /= 2;
				minSize /= 2;
			}
			int mip = 8;
			if (texture.flag == 0x05)
				mip = 10;
			else if (texture.flag == 0x03)
				mip = 7;
			else if (texture.flag == 0x01)
				mip = 3;
			texture.mip = mip;
			long mipSize = size;
			for (int s = 2; s <= mip; s++) {
				mipSize /= 4;
				mipSize = (mipSize < minSize) ? minSize : mipSize;
				size += mipSize;
			}
			texture.dataSize = size;
			dataFile.textures[i] = texture;
			if (IS_DEBUG) {
				System.out.printf("Pack Texture-%d: %s-%d-%d-%X-%X\n", i, texture.pixelFormat, texture.width,
						texture.height, texture.dataStart, texture.dataSize);
			}
		}
	}

	/**
	 * Read Models from Package File
	 * @param in
	 * @param dataFile
	 * @param dataBaseAddr
	 * @throws IOException
	 */
	private void readPackageModels(ByteFileReader in, N2kPackageFile dataFile, long dataBaseAddr) throws IOException {
		//read 3d model headers
		in.seek(dataFile.modelHeaderStart);
		long curPos = in.getFilePointer();
		//curPos += 0xD8;
		dataFile.models = new N2k3DModelPart[dataFile.modelNum];
		if (IS_DEBUG) {
			System.out.printf("ModelHeader:%X\n", curPos);
		}
		for (int i = 0; i < dataFile.modelNum; i++) {
			in.seek(curPos + i * 0xE0);
			//read model data address
			if (IS_DEBUG) {
				System.out.printf("Model %d: pos:%X, ", i, in.getFilePointer());
			}
			N2k3DModelPart part = new N2k3DModelPart();
			part.modelDef1 = dataFile.modelDef1;
			part.modelDef2 = dataFile.modelDef2;
			part.modelVersion = dataFile.modelVersion;
			part.no = i;
			in.skipBytes(0x0C);
			int defSize = 0x30;
			int defType = in.read4BytesAsInt();
			switch (defType) {
			case 0x10:
				defSize = 0x40;
				break;
			case 0x11:
				defSize = 0x40;
				break;
			}
			if (IS_DEBUG) {
				System.out.printf("defType:%X, chunkCount:%X\n", defType, defSize);
			}
			part.defType = defType;
			part.childDefSize = defSize;
			in.skipBytes(0x6C);
			part.childNum = in.read4BytesAsInt();
			part.childDefAddr = in.getFilePointer() + in.read4Bytes() - 1;
			in.skipBytes(0x04);
			part.modelDefAddr = in.getFilePointer() + in.read4Bytes() - 1;
			in.skipBytes(0x1C);
			part.faceDataSize = in.read4BytesAsInt();
			if (part.faceDataSize == 0) {
				in.skipBytes(0x28);
				part.faceDataSize = in.read4BytesAsInt();
			}
			part.faceDataStart = dataBaseAddr + in.read4Bytes() - 1;
			if (IS_DEBUG) {
				System.out.printf(
						"pos:%X, %X, %X, %X, %X, %X, ChildNum:%d, ChildAddr:%X, DefAddr:%X, %X, faceSize:%d\n",
						in.getFilePointer(), part.unkown1, part.unkown2, part.unkown3, part.unkown4, part.unkown5,
						part.childNum, part.childDefAddr, part.modelDefAddr, part.unkown8, part.faceDataSize);
			}
			//==read child parts definition
			in.seek(part.childDefAddr);
			part.childParts = new N2k3DChildModelPart[part.childNum];
			for (int j = 0; j < part.childNum; j++) {
				in.skipBytes(0x04);
				N2k3DChildModelPart tempPart = new N2k3DChildModelPart();
				tempPart.index = in.read4BytesAsInt();
				tempPart.indicesNum0 = in.read4BytesAsInt();
				tempPart.indicesNum1 = in.read4BytesAsInt();
				in.skipBytes(0x04);
				tempPart.vertStart = in.read4BytesAsInt();
				tempPart.vertNum = in.read4BytesAsInt();
				in.skipBytes(defSize - 0x1C);
				part.childParts[j] = tempPart;
				if (IS_DEBUG) {
					System.out.printf("Child: %d = %d-%d : %d-%d\n", j, tempPart.indicesNum0, tempPart.indicesNum1,
							tempPart.vertStart, tempPart.vertNum);
				}
			}
			//==model definition start
			in.seek(part.modelDefAddr);
			if (IS_DEBUG) {
				System.out.printf("%d - modelDefAddr: %X\n", i, in.getFilePointer());
			}
			in.skipBytes(0x04);
			part.chunkCount = in.read4BytesAsInt() + 1;
			part.vertexNum = in.read4BytesAsInt();
			if (part.vertexNum == 0) {
				part.vertexNum = in.read4BytesAsInt();
			}
			int chunkCount = in.read4BytesAsInt();
			long chunkAddr = in.getFilePointer() + in.read4BytesAsInt() - 1;
			int uvnType = in.read4BytesAsInt();
			part.uvnDefType = uvnType;
			int nextChunkAddr = in.read4BytesAsInt();
			int unkown2 = in.read4BytesAsInt();
			in.seek(chunkAddr);
			part.dataChunkCount = part.chunkCount;
			if (IS_DEBUG) {
				System.out
						.printf("[%d - chunkCount:%d, chunkAddr:%X, uvnType:%d, nextChunkAddr:%X, unkown2:%d, vertexCount:%d]\n",
								i, part.dataChunkCount, chunkAddr, uvnType, nextChunkAddr, unkown2, part.vertexNum);
			}
			in.skipBytes(0x08);
			long vertexBaseAddr = 0;
			if (part.chunkCount == 1) {
				part.perVertexSize = in.read4BytesAsInt();
				in.skipBytes(0x04);
				vertexBaseAddr = in.getFilePointer();
				part.vertexDataStart = dataBaseAddr + in.read4Bytes() - 1;
				in.skipBytes(0x3C);
				part.perUVNSize = -1;
				part.uvnDataSize = -1;
				part.uvnDataStart = -1;
			} else {
				part.perVertexSize = in.read4BytesAsInt();
				in.skipBytes(0x04);
				vertexBaseAddr = in.getFilePointer();
				part.vertexDataStart = vertexBaseAddr + in.read4BytesAsInt() - 1;
				in.skipBytes(0x3C);
				part.perUVNSize = in.read4BytesAsInt();
				part.uvnDataSize = in.read4BytesAsInt();
				part.uvnDataStart = dataBaseAddr + in.read4Bytes() - 1;
			}
			if (IS_DEBUG) {
				System.out.printf("ReadIndex: [uv:%X, vrt:%X, base:%X]\n", part.uvnDataStart, part.vertexDataStart,
						dataBaseAddr);
			}
			/*if (i > 0) {
				N2k3DModelPart prePart = dataFile.models[i - 1];
				part.faceDataStart = prePart.uvnDataStart + prePart.uvnDataSize;
			}*/
			if (IS_DEBUG) {
				System.out.printf("%d : ModelInfo[%d]: Verts: %X-%d-%d + %X-%d-%d, Faces: %X-%d\n", i,
						part.dataChunkCount, part.vertexDataStart, part.vertexNum, part.perVertexSize,
						part.uvnDataStart, part.uvnDataSize, part.perUVNSize, part.faceDataStart, part.faceDataSize);
			}
			//definition whether import uv datas(uv datas is invalid)
			part.isUVvalid = false;
			int perVertexSize = part.perVertexSize;
			int perUVNSize = part.perUVNSize;
			int uvType = perVertexSize - 12;
			if (part.dataChunkCount == 1) {
				if (uvType == 0x28 || uvType == 0x24) {
					part.isUVvalid = true;
				}
			} else {
				uvType = perUVNSize;
				if (perUVNSize == 0x18 || perUVNSize == 0x24 || perUVNSize == 0x28 || perUVNSize == 0x04) {
					part.isUVvalid = true;
				}
			}
			if (IS_DEBUG) {
				System.out.printf("UVType: %X - %s\n", uvType, part.isUVvalid);
			}
			dataFile.models[i] = part;
		}
	}
}
